///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include <Scene/Object.h>
#include <Scene/SceneManager.h>
#include <Model/Mesh.h>
#include <Scene/Culling/Culler.h>
#include <tinyxml.h>
#include <Material/MaterialManager.h>
#include <SettingsManager.h>

#include <Model/MeshManager.h>
#include <Material/Material.h>
#include <Shader/ShaderManager.h>
#include <Texture/TextureManager.h>
#include <Scene/Culling/Sphere.h>
#include <Texture/Texture.h>
#include <Profiling/ProfilingManager.h>

//-----------------------------------------------------------------------------

Object::Object()
	:	m_parent( NULL )
	,	m_isVisible( true )
{
	m_relativeTransform.SetIdentity();
	m_absoluteTransform.SetIdentity();
}

//-----------------------------------------------------------------------------

Object::~Object()
{
	if( m_parent )
	{
		m_parent->RemoveChild( this );
	}
	//TODO - what to do with children
	std::vector<Object*>::iterator it_obj = m_children.begin();
	for( ; it_obj != m_children.end(); it_obj++ )
	{
		SAFE_DEL(*it_obj);
	}

}

//-----------------------------------------------------------------------------

void		Object::Update(f32 dt)
{
	//do my update
	updateAbsoluteTransform();

	//do children update
	std::vector<Object*>::iterator it_obj = m_children.begin();
	for( ; it_obj != m_children.end(); it_obj++ )
	{
		(*it_obj)->Update(dt);
	}
}

//-----------------------------------------------------------------------------

void		Object::Draw()
{
	MGL_PROFILE("Object::Draw");

	//glPushMatrix();
	//GL_CHECK_ERRORS();

	////move/rotate/scale
	//glTranslatef( getPosition().getX(), getPosition().getY(), getPosition().getZ() );
	//GL_CHECK_ERRORS();
	//glRotatef( getRotation().getY(), 0, 1, 0 );
	//GL_CHECK_ERRORS();
	//glRotatef( getRotation().getX(), 1, 0, 0 );
	//GL_CHECK_ERRORS();
	//glRotatef( getRotation().getZ(), 0, 0, 1 );
	//GL_CHECK_ERRORS();
	//glScalef( getScale().getX(), getScale().getY(), getScale().getZ() );
	//GL_CHECK_ERRORS();

	//my draw
	//ASSERT( m_sceneManager->GetCuller() );
	//if( m_sceneManager->GetCuller()->ShouldDrawObject( this ) )
	{
		std::vector<Mesh*>::iterator it_mesh = m_meshes.begin();
		for( ; it_mesh != m_meshes.end(); it_mesh++ )
		{
			(*it_mesh)->draw( this );
//#if defined(DEBUG)
//			if( (*it_mesh)->GetBoundingSphere() )
//			{
//				(*it_mesh)->GetBoundingSphere()->draw( this );
//			}
//#endif
		}
	}
	
	//children draw
	std::vector<Object*>::iterator it_obj = m_children.begin();
	for( ; it_obj != m_children.end(); it_obj++ )
	{
		(*it_obj)->Draw();
	}

	//glPopMatrix();
	//GL_CHECK_ERRORS();
}

//-----------------------------------------------------------------------------

void		Object::AddChild(Object* child)
{
	std::vector<Object*>::iterator iter;
	for( iter = m_children.begin(); iter != m_children.end(); iter++ )
	{
		if( *iter == child )
		{
			return;
		}
	}
	m_children.push_back( child );
	child->SetSceneManager( m_sceneManager );
	child->SetParent( this );
}

//-----------------------------------------------------------------------------

void		Object::RemoveChild(Object* child)
{
	std::vector<Object*>::iterator iter;
	for( iter = m_children.begin(); iter != m_children.end(); iter++ )
	{
		if( *iter == child )
		{
			(*iter)->SetSceneManager( NULL );
			m_children.erase( iter );
		}
	}
}

//-----------------------------------------------------------------------------

std::vector<Object*>&	Object::GetChildren()
{
	return m_children;
}

//-----------------------------------------------------------------------------

void		Object::SetParent(Object* parent)
{
	m_parent = parent;
}

//-----------------------------------------------------------------------------

Object*		Object::GetParent()
{
	return m_parent;
}

//-----------------------------------------------------------------------------

void		Object::SetVisible(bool visible)
{
	m_isVisible = visible;
}

//-----------------------------------------------------------------------------

bool		Object::IsVisible()
{
	return m_isVisible;
}

//-----------------------------------------------------------------------------

void		Object::AddMesh( Mesh*	mesh )
{
	m_meshes.push_back( mesh );
}

//-----------------------------------------------------------------------------

u16			Object::GetMeshCount()
{
	return m_meshes.size();
}

//-----------------------------------------------------------------------------

Mesh*		Object::GetMesh( u16 index )
{
	ASSERT( index < m_meshes.size() );
	return m_meshes[ index ];
}

//-----------------------------------------------------------------------------

//void		Object::SetMesh( Mesh*	mesh )
//{
//	m_mesh = mesh;
//}
//
////-----------------------------------------------------------------------------
//
//Mesh*		Object::GetMesh()
//{
//	return m_mesh;
//}

//-----------------------------------------------------------------------------

Matrix4x4	Object::GetRelativeTransform()
{
	if( hasMoved() )
	{
		updateRelativeTransform();
	}
	return m_relativeTransform;
}

//-----------------------------------------------------------------------------

void		Object::SetRelativeTransform(const Matrix4x4& mat)
{
	m_relativeTransform = mat;
}

//-----------------------------------------------------------------------------

Matrix4x4	Object::GetAbsoluteTransform()
{
	return m_absoluteTransform;
}

//-----------------------------------------------------------------------------

void		Object::updateAbsoluteTransform()
{
	Matrix4x4 mat;
	if( m_parent )
	{
		mat = m_parent->GetAbsoluteTransform();
	}
	else
	{
		mat.SetIdentity();
	}
	m_absoluteTransform = mat * GetRelativeTransform();
}

//-----------------------------------------------------------------------------

void		Object::updateRelativeTransform()
{
	Vec3f pos = getPosition();
	Vec3f rot = getRotation();
	Vec3f scale = getScale();

	m_relativeTransform.SetIdentity();
	//m_relativeTransform.Translate( pos.x, pos.y, pos.z );
	m_relativeTransform.Rotate( rot.y, 0,1,0 );
	m_relativeTransform.Rotate( rot.x, 1,0,0 );
	m_relativeTransform.Rotate( rot.z, 0,0,1 );
	m_relativeTransform.Scale( scale.x, scale.y, scale.z );
	m_relativeTransform.SetPosition( pos );

	setMoved( false );
}

//-----------------------------------------------------------------------------

SceneManager*	Object::GetSceneManager()
{
	return m_sceneManager;
}

//-----------------------------------------------------------------------------

void		Object::SetSceneManager(SceneManager* scene)
{
	m_sceneManager = scene;

	std::vector<Object*>::iterator iter;
	for( iter = m_children.begin(); iter != m_children.end(); iter++ )
	{
		(*iter)->SetSceneManager( m_sceneManager );
	}
}

//-----------------------------------------------------------------------------

void		Object::ParseElement(TiXmlElement* elem)
{
	if( elem )
	{
		//ASSERT( !strcmp( elem->Value(), "object" )  );
		TiXmlElement* node = NULL;

		//mesh
		node = elem->FirstChildElement( "mesh" );
		if( node )
		{
			std::string filename = node->Attribute( "file" );
			MeshData* meshData = MeshManager::GetInstance()->GetMesh( filename );
			Material* material = MaterialManager::GetInstance()->GetMaterial( filename );
			Mesh* mesh = MGL_NEW Mesh( filename );
			mesh->SetMeshData( meshData );
			mesh->SetMaterial( material );
			AddMesh( mesh );
		}

		//material
		node = elem->FirstChildElement( "material" );
		if( node )
		{
			Mesh* mesh = GetMesh( 0 );
			if( mesh )
			{
				Material* mat = mesh->GetMaterial();

				if( !mat )
				{
					const char* ccname = NULL;
					ccname = node->Attribute( "name" );
					if( ccname )
					{
						std::string name = ccname;
						mat = MaterialManager::GetInstance()->GetMaterial( name );
					}

					if( !mat )
					{
						mat = MGL_NEW Material();
						if( ccname )
						{
							mat->SetName( ccname );
						}
					}
				}

				if( mat )
				{
					mesh->SetMaterial( mat );

					TiXmlElement* inmat = NULL;
					
					//shader
					inmat = node->FirstChildElement( "shader" );
					while( inmat )
					{
						if( inmat->Attribute( "name" ) )
						{
							std::string shadername = inmat->Attribute( "name" );
							GLint	pass = 0;
							inmat->Attribute( "pass", &pass );
							GPUProgram* shader = ShaderManager::GetInstance()->GetShader( shadername );
							if( !shader )
							{
								DEBUG_WAR( "[Object::PareseElement] Could not find '%s' shader!", shadername.c_str() );
							}
							else
							{
								ASSERT( pass >= 0 && pass <= 200 );
								mat->m_shaders[(u8)pass] = shader;
							}
						}

						inmat = inmat->NextSiblingElement( "shader" );
					}

					//texture
					inmat = node->FirstChildElement( "texture" );
					while( inmat )
					{
						Texture* tex = NULL;
						if( inmat->Attribute( "file" ) )
						{
							std::string filename = inmat->Attribute( "file" );
							tex = TextureManager::GetInstance()->GetTexture( filename );
							if( !tex )
							{
								DEBUG_WAR( "[Object::PareseElement] Could not find '%s' texture!", filename.c_str() );
							}
						}
						else if( inmat->Attribute( "name" ) )
						{
							std::string name = inmat->Attribute( "name" );
							tex = MGL_NEW Texture( name );
							TextureManager::GetInstance()->AddTexture( tex );
						}

						if( tex )
						{
							if( inmat->Attribute( "unit" ) )
							{
								u32 unit = 0;
								sscanf( inmat->Attribute( "unit" ), "%u", &unit );
								if( mat->m_textures.find( unit ) == mat->m_textures.end() )
								{
									mat->m_textures[ unit ] = tex;
								}
								else
								{
									DEBUG_WAR( "[Object::PareseElement] Texture unit '%d' already used!", unit );
								}
							}
							else
							{
								//TODO unused texture ... delete it?
							}
						}

						inmat = inmat->NextSiblingElement( "texture" );
					}

					//difuse
					inmat = node->FirstChildElement( "diffuse" );
					if( inmat )
					{
						double r,g,b,a,all;
						r = g = b = a = all = 1;
						inmat->Attribute( "r", &r );
						inmat->Attribute( "g", &g );
						inmat->Attribute( "b", &b );
						inmat->Attribute( "a", &a );
						if( inmat->Attribute( "all", &all ) )
						{
							r = g = b = a = all;
						}
						mat->m_diffuse[0] = r;
						mat->m_diffuse[1] = g;
						mat->m_diffuse[2] = b;
						mat->m_diffuse[3] = a;
					}
				}
			}
		}

		//position
		node = elem->FirstChildElement( "position" );
		if( node )
		{
			double x,y,z,all;
			x = y = z = all = 0;
			node->Attribute( "x", &x );
			node->Attribute( "y", &y );
			node->Attribute( "z", &z );
			if( node->Attribute( "all", &all ) )
			{
				x = y = z = all;
			}
			setPosition( (f32)x, (f32)y, (f32)z );
		}
		
		//rotation
		node = elem->FirstChildElement( "rotation" );
		if( node )
		{
			double x,y,z,all;
			x = y = z = all = 0;
			node->Attribute( "x", &x );
			node->Attribute( "y", &y );
			node->Attribute( "z", &z );
			if( node->Attribute( "all", &all ) )
			{
				x = y = z = all;
			}
			setRotation( (f32)x, (f32)y, (f32)z );
		}
		
		//scale
		node = elem->FirstChildElement( "scale" );
		if( node )
		{
			double x,y,z,all;
			x = y = z = all = 1;
			node->Attribute( "x", &x );
			node->Attribute( "y", &y );
			node->Attribute( "z", &z );
			if( node->Attribute( "all", &all ) )
			{
				x = y = z = all;
			}
			setScale( (f32)x, (f32)y, (f32)z );
		}
		
		//children
		node = elem->FirstChildElement( "object" );
		while( node )
		{
			Object* child = MGL_NEW Object();
			AddChild( child );
			child->ParseElement( node );
			node = node->NextSiblingElement( "object" );
		}
	}
}

//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
